package com.letv.whatslive.web.common;


import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.*;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.HttpResponseException;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntity;
import org.apache.http.entity.mime.content.ContentBody;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.PoolingClientConnectionManager;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.params.SyncBasicHttpParams;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.SocketTimeoutException;
import java.net.URLDecoder;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 * Created by qiyongpeng on 2015/11/25.
 */

public class HttpUtils {

    private static class CharsetableResponseHandler implements ResponseHandler<String> {

        private String defaultEncoding;

        public CharsetableResponseHandler(String defaultEncoding) {
            this.defaultEncoding = defaultEncoding;
        }

        @Override
        public String handleResponse(HttpResponse response) throws ClientProtocolException, IOException {
            StatusLine statusLine = response.getStatusLine();
            HttpEntity entity = response.getEntity();
            if (statusLine.getStatusCode() >= 300) {
                EntityUtils.consume(entity);
                throw new HttpResponseException(statusLine.getStatusCode(), statusLine.getReasonPhrase());
            }
            return entity == null ? null : EntityUtils.toString(entity, defaultEncoding);
        }
    }

    private static HttpClient client;

    private static final int CONNECTION_TIMEOUT = 1000;

    private static PoolingClientConnectionManager connectionManager;
    private static final Logger logger = LoggerFactory.getLogger(HttpUtils.class);

    private static final int READ_TIMEOUT = 10000;

    private static final int DEFAULT_MAX_PER_ROUTE = 100;

    private static final int MAX_TOTAL = 400;
    static {
        HttpUtils.connectionManager = new PoolingClientConnectionManager();
        HttpUtils.connectionManager.setDefaultMaxPerRoute(DEFAULT_MAX_PER_ROUTE);
        HttpUtils.connectionManager.setMaxTotal(MAX_TOTAL);
        HttpParams defaultParams = new SyncBasicHttpParams();

        defaultParams.setParameter(CoreConnectionPNames.SO_TIMEOUT, HttpUtils.READ_TIMEOUT);
        defaultParams.setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, HttpUtils.CONNECTION_TIMEOUT);

        HttpProtocolParams.setContentCharset(defaultParams, Consts.UTF_8.name());
        HttpProtocolParams.setUseExpectContinue(defaultParams, false);
        DefaultHttpClient.setDefaultHttpParams(defaultParams);
        HttpUtils.client = new DefaultHttpClient(HttpUtils.connectionManager, defaultParams);
        HttpUtils.client = wrapHttpsClient(client);
    }

    private static String getDefaultEncoding(String defaultEncoding) {
        String encoding = StringUtils.isEmpty(defaultEncoding) ? Consts.UTF_8.name() : defaultEncoding;
        return encoding;
    }

    /**
     * 封装支持https
     *
     * @param base
     * @return
     */
    private static HttpClient wrapHttpsClient(HttpClient base) {
        try {
            SSLContext ctx = SSLContext.getInstance("TLS");
            X509TrustManager tm = new X509TrustManager() {
                public X509Certificate[] getAcceptedIssuers() {
                    return null;
                }

                public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
                }

                public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
                }
            };
            ctx.init(null, new TrustManager[]{tm}, null);
            SSLSocketFactory ssf = new SSLSocketFactory(ctx, SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
            SchemeRegistry registry = new SchemeRegistry();
            registry.register(new Scheme("https", 443, ssf));
            registry.register(new Scheme("http", 80, PlainSocketFactory.getSocketFactory()));
            ThreadSafeClientConnManager mgr = new ThreadSafeClientConnManager(registry);
            mgr.setDefaultMaxPerRoute(HttpUtils.DEFAULT_MAX_PER_ROUTE);
            mgr.setMaxTotal(HttpUtils.MAX_TOTAL);
            return new DefaultHttpClient(mgr, base.getParams());
        } catch (Exception ex) {
            ex.printStackTrace();
            return null;
        }
    }


    /**
     * 请求特定的url提交表单，使用post方法，返回响应的内容
     *
     * @param url
     * @param formData 表单的键值对
     * @return
     */
    public static String post(String url, Map<String, String> formData, Integer readTimeout) {
        return HttpUtils.post(url, formData, null, readTimeout);
    }

    /**
     * 请求特定的url提交表单，使用post方法，返回响应的内容
     *
     * @param url
     * @param formData 表单的键值对
     * @return
     */
    public static String post(String url, Map<String, String> formData) {
        return HttpUtils.post(url, formData, null, null);
    }

//    public static String post(String url, Map<String, String> formData,Map<String,String> headers) {
//        return HttpUtils.post(url, formData, null);
//    }

    /**
     * 请求特定的url提交表单，使用post方法，返回响应的内容，超时重试指定次数
     *
     * @param url
     * @param formData
     * @param retryTimes
     * @return
     */
    public static String timeoutRetryPost(String url, Map<String, String> formData, int retryTimes) {
        if (retryTimes < 0) {
            throw new IllegalArgumentException("retry times must great than or equal 0");
        }
        SocketTimeoutException ste = null;
        for (int i = 0; i <= retryTimes; i++) {
            try {
                String result = postWithTimeoutException(url, formData, null,null, null);
                if (i > 0) {
                    logger.warn("post [%s] retry %d times", url, i);
                }
                return result;
            } catch (SocketTimeoutException e) {
                ste = e;
            }
        }
        logger.error("post [%s] timeout, retry %d times", url, retryTimes, ste);
        return null;
    }

    /**
     * 指定headers的post请求
     * @param url
     * @param formData
     * @param headers
     * @return
     */
    public static String appointHeadersPost(String url, Map<String, String> formData,Map<String,String> headers) {
        try {
            return postWithTimeoutException(url, formData, null,headers, null);
        } catch (SocketTimeoutException e) {
            HttpUtils.logger.error(String.format("post [%s] timeout", url), e);
            return null;
        }
    }

    /**
     * 请求特定的url提交表单，使用post方法，返回响应的内容
     *
     * @param url
     * @param formData        表单的键值对
     * @param defaultEncoding 处理 form encode 的编码，以及作为 contentType 的默认编码
     * @return
     */
    public static String post(String url, Map<String, String> formData, String defaultEncoding, Integer readTimeout) {
        try {
            return postWithTimeoutException(url, formData, defaultEncoding, null, readTimeout);
        } catch (SocketTimeoutException e) {
            HttpUtils.logger.error(String.format("post [%s] timeout", url), e);
            return null;
        }
    }

    private static String postWithTimeoutException(String url, Map<String, String> formData, String defaultEncoding,Map<String, String> headers, Integer readTimeout)
            throws SocketTimeoutException {
        defaultEncoding = HttpUtils.getDefaultEncoding(defaultEncoding);

        HttpPost post = new HttpPost(url);
        if (readTimeout != null && readTimeout > 0) {
            post.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT, readTimeout);
        }
        String content = null;
        List<NameValuePair> nameValues = new ArrayList<NameValuePair>();
        if (formData != null && !formData.isEmpty()) {
            for (Entry<String, String> entry : formData.entrySet()) {
                nameValues.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
            }
        }

        if (headers != null) {
            for (Entry<String, String> entry : headers.entrySet()) {
                post.addHeader(entry.getKey(), entry.getValue());
            }
        }
        try {
            UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(nameValues, defaultEncoding);
            post.setEntity(formEntity);
            content = HttpUtils.client.execute(post, new CharsetableResponseHandler(defaultEncoding));
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("unsupported Encoding " + defaultEncoding, e);
        } catch (SocketTimeoutException e) {
            throw e;
        } catch (Exception e) {
            HttpUtils.logger.error(String.format("post [%s] happens error ", url), e);
        }
        return content;
    }

    /**
     * 请求特定的url提交Json字符串，使用post方法，返回响应的内容
     *
     * @param url
     * @param jsonData
     * @return
     */
    public static String post(String url, String jsonData){
        return HttpUtils.post(url, jsonData, null);
    }

    /**
     * 请求特定的url提交Json字符串，使用post方法，返回响应的内容
     *
     * @param url
     * @param jsonData
     * @return
     */
    public static String post(String url, String jsonData, String defaultEncoding){
        defaultEncoding = HttpUtils.getDefaultEncoding(defaultEncoding);
        String content = null;
        try {
            HttpPost post = new HttpPost(url);
            StringEntity entity = new StringEntity(jsonData, defaultEncoding);
            entity.setContentType(new BasicHeader(HTTP.CONTENT_TYPE, "application/json"));
            post.setEntity(entity);
            HttpResponse response = HttpUtils.client.execute(post);
            content = EntityUtils.toString(response.getEntity());
        } catch (Exception e) {
            HttpUtils.logger.error(String.format("post [%s] happens error ", url), e);
        }
        return content;
    }


    /**
     * 请求特定的url，返回响应的内容
     *
     * @param url
     * @return
     */
    public static String request(String url) {
        return HttpUtils.request(url, null, null);
    }

    public static String request(String url, Map<String, Object> params){
        return HttpUtils.request(url + createQueryString(params), null, null);
    }

    private static String createQueryString(Map<String, Object> params){
        StringBuilder sb = new StringBuilder();
        if(!CollectionUtils.isEmpty(params)){
            sb.append("?");
            for(Iterator<Entry<String, Object>> iter = params.entrySet().iterator(); iter.hasNext();){
                Entry<String, Object> entry = iter.next();
                sb.append(entry.getKey());
                sb.append("=");
                sb.append(entry.getValue());
                if(iter.hasNext()){
                    sb.append("&");
                }
            }
        }
        return sb.toString();
    }

    /**
     * 请求特定的url，返回响应的内容
     *
     * @param url
     * @param readTimeout
     * @return
     */
    public static String request(String url, Integer readTimeout) {
        return HttpUtils.request(url, null, readTimeout);
    }

    /**
     * 请求特定的url，返回响应的内容
     *
     * @param url
     * @param defaultEncoding 如果返回的 contentType 中没有指定编码，则使用默认编码
     * @param readTimeout:    socket timeout
     * @return
     */
    public static String request(String url, String defaultEncoding, Integer readTimeout) {
        defaultEncoding = HttpUtils.getDefaultEncoding(defaultEncoding);

        HttpGet get = new HttpGet(url);
        if (readTimeout != null && readTimeout > 0) {
            get.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT, readTimeout);
        }
        String content = null;
        try {
            content = HttpUtils.client.execute(get, new CharsetableResponseHandler(defaultEncoding));
        } catch (Exception e) {
            HttpUtils.logger.error(String.format("request [%s] happens error ", url), e);
        }
        return content;
    }

    /**
     * 请求特定的url，返回响应的内容
     *
     * @param url
     * @param defaultEncoding 如果返回的 contentType 中没有指定编码，则使用默认编码
     * @param readTimeout:    socket timeout
     * @return
     * @throws SocketTimeoutException
     * @throws ConnectTimeoutException
     */
    public static String requestDealTimeOutException(String url, String defaultEncoding, Integer readTimeout)
            throws SocketTimeoutException, ConnectTimeoutException {
        defaultEncoding = HttpUtils.getDefaultEncoding(defaultEncoding);

        HttpGet get = new HttpGet(url);
        if (readTimeout != null && readTimeout > 0) {
            get.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT, readTimeout);
        }
        String content = null;
        try {
            content = HttpUtils.client.execute(get, new CharsetableResponseHandler(defaultEncoding));
        } catch(SocketTimeoutException e){
            HttpUtils.logger.error(String.format("requestDealTimeOutException [%s] happens error ", url), e);
            throw  e;
        } catch(ConnectTimeoutException e){
            HttpUtils.logger.error(String.format("requestDealTimeOutException [%s] happens error ", url), e);
            throw  e;
        } catch (Exception e) {
            HttpUtils.logger.error(String.format("requestDealTimeOutException [%s] happens error ", url), e);
        }finally {
            if(get!=null){
                get.releaseConnection();
            }
        }
        return content;
    }

    /**
     * 对url解码
     *
     * @param str    需要解码的字符串
     * @param encode 编码，如 GBK, UTF-8 等
     * @return 如果解码出错，会传回 null；如果 str 为空，则也返回 null
     */
    public static String urlDecode(String str, String encode) {
        if (StringUtils.isEmpty(str)) {
            return null;
        }
        String result = null;
        try {
            result = URLDecoder.decode(str, encode);
        } catch (UnsupportedEncodingException e) {
            HttpUtils.logger.error("urldecode error for {} with encode {}", str, encode);
        }
        return result;
    }

    /**
     * 对特定的url的post请求，返回响应体
     *
     * @param url      请求的url
     * @param formData 请求参数
     * @return
     */
    public static HttpResponse postMapForResponse(String url, Map<String, String> formData) {
        String encoding = HttpUtils.getDefaultEncoding(null);

        HttpPost post = new HttpPost(url);
        List<NameValuePair> nameValues = new ArrayList<NameValuePair>();
        if (formData != null && !formData.isEmpty()) {
            for (Entry<String, String> entry : formData.entrySet()) {
                nameValues.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
            }
        }
        try {
            UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(nameValues, encoding);
            post.setEntity(formEntity);
            return HttpUtils.client.execute(post);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("unsupported Encoding " + encoding, e);
        } catch (SocketTimeoutException e) {
            logger.error("error when postForResponse " + e);
        } catch (Exception e) {
            HttpUtils.logger.error(String.format("post [%s] happens error ", url), e);
        }
        return null;
    }

    /**
     * 对特定的url的post请求，返回响应体
     *
     * @param url      请求的url
     * @return
     */
    public static HttpResponse get(String url) {
        HttpGet get = new HttpGet(url);
        try {
            return HttpUtils.client.execute(get);
        } catch (SocketTimeoutException e) {
            logger.error("error when get " + e);
        } catch (Exception e) {
            HttpUtils.logger.error(String.format("get [%s] happens error ", url), e);
        }
        return null;
    }


    private HttpUtils() {
    }

    /**
     * 请求特定的url提交Json字符串，使用post方法，返回响应的内容
     *
     * @param url
     * @param jsonData
     * @return
     */
    public static String requestTimeOutPost(String url, String jsonData)
            throws SocketTimeoutException, ConnectTimeoutException{
        String defaultEncoding = HttpUtils.getDefaultEncoding(null);
        String content = null;
        HttpPost post = null;
        try {
            post = new HttpPost(url);
            StringEntity entity = new StringEntity(jsonData, defaultEncoding);
            entity.setContentType(new BasicHeader(HTTP.CONTENT_TYPE, "application/json"));
            post.setEntity(entity);
            HttpResponse response = HttpUtils.client.execute(post);
            content = EntityUtils.toString(response.getEntity());
        } catch(SocketTimeoutException e){
            HttpUtils.logger.error(String.format("requestTimeOutPost [%s] happens error ", url), e);
            throw  e;
        } catch(ConnectTimeoutException e){
            HttpUtils.logger.error(String.format("requestTimeOutPost [%s] happens error ", url), e);
            throw  e;
        } catch (Exception e) {
            HttpUtils.logger.error(String.format("requestTimeOutPost [%s] happens error ", url), e);
        }finally {
            if(post!=null){
                post.releaseConnection();
            }
        }
        return content;
    }

    public static byte[] downloadFile (String url, Integer readTimeout){
        HttpGet get = new HttpGet(url);
        if (readTimeout != null && readTimeout > 0) {
            get.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT, readTimeout);
        }
        HttpResponse response = null;
        try {
            response = client.execute(get);
            HttpEntity entity = response.getEntity();
            return IOUtils.toByteArray(entity.getContent());
        } catch (IOException e) {
            logger.error(String.format("download file error, [%s]", url), e);
        }finally {
            get.releaseConnection();
        }
        return null;
    }


    public interface ResultProcesser<T>{
        public void process(T t);
    }

    public static void openFileInputStream (String url, Integer readTimeout, ResultProcesser processer){
        HttpGet get = new HttpGet(url);
        if (readTimeout != null && readTimeout > 0) {
            get.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT, readTimeout);
        }
        HttpResponse response = null;
        try {
            response = client.execute(get);
            HttpEntity entity = response.getEntity();
            if(processer != null) {
                processer.process(entity.getContent());
            }
        } catch (IOException e) {
            logger.error(String.format("open file inputstream error, [%s]", url), e);
        }finally {
            get.releaseConnection();
        }
    }


    public static String uploadFile(String url, Map<String, ContentBody> formData, Map<String, String> headers, Integer readTimeout){
        String content = null;
        HttpPost post = new HttpPost(url);
        if (readTimeout != null && readTimeout > 0) {
            post.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT, readTimeout);
        }
        if (headers != null) {
            for (Entry<String, String> entry : headers.entrySet()) {
                post.addHeader(entry.getKey(), entry.getValue());
            }
        }
        try {
            MultipartEntity entity = new MultipartEntity();
            if(formData != null) {
                for (String key : formData.keySet()) {
                    entity.addPart(key, formData.get(key));
                }
            }
            post.setEntity(entity);
            content = HttpUtils.client.execute(post, new CharsetableResponseHandler(HttpUtils.getDefaultEncoding(null)));
        } catch (SocketTimeoutException e) {
            HttpUtils.logger.error(String.format("upload file timeout, [%s]", url), e);
        } catch (Exception e) {
            HttpUtils.logger.error(String.format("upload file error, [%s]", url), e);
        }
        return content;
    }

}

